package jmine.tec.hydra.domain.specification;

import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;

import jmine.tec.hydra.HydraController;
import jmine.tec.hydra.HydraMessages;
import jmine.tec.hydra.api.HydraSpecification;
import jmine.tec.hydra.codec.Decoder;
import jmine.tec.hydra.codec.Encoder;
import jmine.tec.hydra.codec.impl.ScriptDecoder;
import jmine.tec.hydra.codec.impl.ScriptEncoder;
import jmine.tec.hydra.domain.HydraSystem;
import jmine.tec.hydra.domain.senderRegistry.AbstractSenderRegistry;
import jmine.tec.hydra.exception.HydraException;
import jmine.tec.hydra.send.Sender;
import jmine.tec.persist.annotation.Alias;
import jmine.tec.persist.annotation.Comment;
import jmine.tec.persist.annotation.Constraint;
import jmine.tec.persist.annotation.DiscriminatorComment;
import jmine.tec.persist.annotation.Index;
import jmine.tec.persist.annotation.UniqueConstraint;
import jmine.tec.script.persistent.domain.Script;

import org.hibernate.annotations.ForeignKey;

import bancosys.tec.component.Documentation;
import bancosys.tec.persist.bussobj.PersistableBusinessObject;

/**
 * Especificação que utiliza arquivos de script e template para gerar uma mensagem xml, e efetua o parse de uma mensagem xml de resposta.
 * 
 * @author piercio
 */
@Entity
@Table(name = "HYDRA_SPEC")
@Alias("HYDSPC")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "TP", discriminatorType = DiscriminatorType.INTEGER)
@SequenceGenerator(name = "SEQ_HYDSPC", sequenceName = "SEQ_HYDSPC")
@UniqueConstraint(name = "AK_HYDSPC_0", columns = { "ACTION", "COD_HYDRA_SYSTEM" })
@Comment(table = "HYDRA_SPEC", column = "TP", value = "TIPO DE ESPECIFICACAO.")
@DiscriminatorValue("0")
@DiscriminatorComment("SIMPLES")
@Constraint(suffix = "0")
@Documentation("ESPECIFICAÇAO DE RELACAO ACAO/SISTEMA.")
public class HydraSpecificationImpl extends PersistableBusinessObject implements HydraSpecification {

    private Long id;

    private String action;

    private HydraSystem system;

    private AbstractSenderRegistry senderRegistry;

    private Script encodeScript;

    private Script decodeScript;

    private Script template;

    private boolean enabled;

    /**
     * Construtor para o Hibernate.
     */
    protected HydraSpecificationImpl() {
        this(null, null);
    }

    /**
     * @param action ação.
     * @param system sistema.
     */
    public HydraSpecificationImpl(String action, HydraSystem system) {
        this.action = action;
        this.system = system;
        this.enabled = true;
    }

    /**
     * @return o valor do id da especificação.
     */
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_HYDSPC")
    @Column(name = "COD_HYDRA_SPEC", nullable = false)
    @Documentation("CODIGO DA ESPECIFICACAO.")
    public Long getId() {
        return this.id;
    }

    /**
     * @param id o valor do id da especificação.
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * @return a ação desta especificação, que gera o ato de envio.
     */
    @Column(name = "ACTION", nullable = false)
    @Documentation("ACAO ESPECIFICADA.")
    public String getAction() {
        return this.action;
    }

    /**
     * @param action a ação desta especificação, que gera o ato de envio
     */
    public void setAction(String action) {
        this.action = action;
    }

    /**
     * @return o sistema destino.
     */
    @ManyToOne
    @Index(suffix = "JT_5")
    @JoinColumn(name = "COD_HYDRA_SYSTEM", nullable = false)
    @Documentation("CODIGO DO SISTEMA ESPECIFICADO.")
    public HydraSystem getSystem() {
        return this.system;
    }

    /**
     * @param system o sistema destino.
     */
    public void setSystem(HydraSystem system) {
        this.system = system;
    }

    /**
     * @return the senderRegistry
     */
    @Index(suffix = "JT_4")
    @ManyToOne
    @JoinColumn(name = "COD_HYDRA_SEND_REG")
    @Documentation("CODIGO DO REGISTRO DE ENVIO DE MENSAGENS UTILIZADO PELA ESPECIFICACAO.")
    public AbstractSenderRegistry getSenderRegistry() {
        return this.senderRegistry;
    }

    /**
     * @param senderRegistry the senderRegistry to set
     */
    public void setSenderRegistry(AbstractSenderRegistry senderRegistry) {
        this.senderRegistry = senderRegistry;
    }

    /**
     * @return o script de codificação em xml.
     */
    @Index(suffix = "JT_1")
    @ManyToOne
    @JoinColumn(name = "COD_HYDRA_SCRIPT_ENCODE")
    @Documentation("CODIGO DO SCRIPT PARA CODIFICAÇAO DA MENSAGEM.")
    @ForeignKey(name = "FK_SCRIPT_HYDSPC_1")
    public Script getEncodeScript() {
        return this.encodeScript;
    }

    /**
     * @param encodeScript o script de codificação em xml.
     */
    public void setEncodeScript(Script encodeScript) {
        this.encodeScript = encodeScript;
    }

    /**
     * @return o script de decodificação de xml.
     */
    @Index(suffix = "JT_0")
    @ManyToOne
    @JoinColumn(name = "COD_HYDRA_SCRIPT_DECODE")
    @Documentation("CODIGO DO SCRIPT PARA DECODIFICAÇAO DA MENSAGEM.")
    @ForeignKey(name = "FK_SCRIPT_HYDSPC_2")
    public Script getDecodeScript() {
        return this.decodeScript;
    }

    /**
     * @param decodeScript o script de decodificação de xml.
     */
    public void setDecodeScript(Script decodeScript) {
        this.decodeScript = decodeScript;
    }

    /**
     * @return o template de velocity para formatação da mensagem.
     */
    @Index(suffix = "JT_2")
    @ManyToOne
    @JoinColumn(name = "COD_HYDRA_SCRIPT_TEMPLATE")
    @Documentation("CODIGO DO SCRIPT DE TEMPLATE PARA CONSTRUCAO DA MENSAGEM.")
    public Script getTemplate() {
        return this.template;
    }

    /**
     * @param template o template de velocity para formatação da mensagem.
     */
    public void setTemplate(Script template) {
        this.template = template;
    }

    // Codecs

    /**
     * @return o codec que transforma um arquivo bsh e um template velocity em uma mensagem xml.
     */
    @Transient
    public Encoder getEncoder() {
        if (this.encodeScript == null || this.template == null) {
            throw new HydraException(HydraMessages.ENCODE_SCRIPTS_NOT_DEFINED.create(this.action, this.system.getMnemonico()));
        }
        return new ScriptEncoder(this.getHydraController(), this.encodeScript, this.template);
    }

    /**
     * @return o sender que grava o xml resultante em um arquivo no diretorio passado.
     */
    @Transient
    public Sender getSender() {
        if (this.senderRegistry == null) {
            throw new HydraException(HydraMessages.SEND_SENDER_NOT_DEFINED.create(this.action, this.system.getMnemonico()));
        }
        return this.senderRegistry.getSender();
    }

    /**
     * {@inheritDoc}
     */
    public boolean hasSender() {
        return this.getSenderRegistry() != null;
    }

    /**
     * @return o decoder que transforma um arquivo xml em uma lista de serviços.
     */
    @Transient
    public Decoder getDecoder() {
        if (this.decodeScript == null) {
            throw new HydraException(HydraMessages.DECODE_SCRIPT_NOT_DEFINED.create(this.action, this.system.getMnemonico()));
        }
        return new ScriptDecoder(this.decodeScript);
    }

    /**
     * Obtém o controller do módulo Hydra
     * 
     * @return controller
     */
    @Transient
    protected HydraController getHydraController() {
        return (HydraController) this.getController();
    }

    /**
     * {@inheritDoc}
     */
    @Constraint(suffix = "JT_0")
    @Column(name = "ENABLED", nullable = false)
    @Documentation("SE ESPECIFICACAO ESTA HABILITADA OU NAO.")
    public boolean isEnabled() {
        return this.enabled;
    }

    /**
     * Setter para o Hibernate.
     * 
     * @param enabled se especificação está habilitada ou não.
     */
    private void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    /**
     * {@inheritDoc}
     */
    public void disable() {
        this.setEnabled(false);
    }

    /**
     * {@inheritDoc}
     */
    public void enable() {
        this.setEnabled(true);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return String.format("HydraSpecificationImpl:%s,%s", this.getSystem(), this.getAction());
    }
}
